﻿using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Threading.Tasks;

namespace LaoLuo.Threading
{
    /// <summary>
    /// provide a queue-mode to handle processes
    /// </summary>
    /// <typeparam name="T"></typeparam>
    public class WorkHandler<T> : IDisposable
    {
        /// <summary>
        /// default handle-amount-per-batch
        /// </summary>
        internal const int DefaultAmountPerBatch = 10;
        private readonly object _lock = new object();
        private readonly int _amountPerBatch = DefaultAmountPerBatch;
        private Action<T> _duty;
        private readonly int _interval;
        private ConcurrentQueue<T> _queue;
        private TimerWithAlarm _timer;

        /// <summary>
        /// init a <see cref="WorkHandler&lt;T&gt;"/>
        /// </summary>
        /// <param name="duty">action to handle a work</param>
        /// <param name="amountPerBatch">amount of work to handle per batch</param>
        /// <param name="interval"></param>
        /// <param name="autoStart"> </param>
        public WorkHandler(Action<T> duty, int amountPerBatch = DefaultAmountPerBatch, int interval = 5000, bool autoStart = true )
        {
            _duty = duty;
            _interval = interval;
            _queue = new ConcurrentQueue<T>();
            if (amountPerBatch > 0)
            {
                _amountPerBatch = amountPerBatch;
            }
            if (autoStart)
                Start();
        }

        /// <summary>
        /// current length of queue
        /// </summary>
        public int QueueCount { get { return _queue.Count; } }

        /// <summary>
        /// current count of work in processing
        /// </summary>
        public int ProcessingCount { get; protected set; }

        /// <summary>
        /// count of work in queue and in processing
        /// </summary>
        public int ToHandleCount { get { return QueueCount + ProcessingCount; } }

        /// <summary>
        /// start working
        /// </summary>
        public void Start()
        {
            _timer = new TimerWithAlarm(HandleJobsOnce, _interval);
            GC.SuppressFinalize(_timer);
        }

        public void Stop()
        {
            lock (_lock)
            {
                _timer.Stop();
                _timer.Dispose();
            }
        }

        /// <summary>
        /// add a work to the queue
        /// </summary>
        /// <param name="item"></param>
        public void AddWork(T item)
        {
            _queue.Enqueue(item);
        }

        /// <summary>
        /// stop the handler and dispose managed resources
        /// </summary>
        public void Dispose()
        {
            lock (_lock)
            {
                Stop();
                _queue = null;
                _duty = null;
            }
        }

        private void HandleJobsOnce()
        {
            TimedLock.TryCall(
                () =>
                    {
#if DEBUG
                    //Console.WriteLine("WorkHandler || working at " + DateTime.Now);
#endif
                        var amount = Math.Min(_amountPerBatch, _queue.Count);
                        var tasks = new List<Task>(amount);
                        ProcessingCount = amount;

                        for (var i = 0; i < amount; i++)
                        {
                            T item;
                            if (_queue.TryDequeue(out item))
                            {
                                tasks.Add(Task.Factory.StartNew(() => _duty(item)).ContinueWith(t =>
                                                                                                    {
                                                                                                        --
                                                                                                            ProcessingCount;
                                                                                                    }
                                              ));
                            }
                        }
                        
                        Task.WaitAll(tasks.ToArray());
                    },
                _lock,
                TimeSpan.FromSeconds(0.1)
                );
        }
    }
}
